home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Internet
/
Collection of Internet.iso
/
infosrvr
/
dev
/
scott
/
WWW
/
NextStep
/
Implementation
/
HyperText.m
< prev
next >
Wrap
Text File
|
1993-07-06
|
49KB
|
1,713 lines
// HyperText Implementation HyperText.m
// ------------------------
//
// HyperText is like Text, but includes links to and from other hypertexts.
//
// Authors:
// TBL Tim Berners-Lee CERN/CN
//
// See also:
// The HTAnchor class, and the HyperAccess class.
//
// History:
// 25 Sep 90 Written (TBL) with the help of the Interface builder.
// 14 Mar 91 Page width is taken from application's page layout.
// Notes.
//
// For all anchors to have addresses, the node must be made with
// newAnchor:Server: . Do not use any other creation methods inherited.
#import <appkit/appkit.h>
#import "HyperText.h"
#import "HyperManager.h"
#import "HTUtils.h"
#import "HTParse.h"
#import "HTStyle.h"
#import "HTAccess.h"
@implementation HyperText
#define DEFAULT_TITLE "World-Wide Web Document"
#define ANCHOR_ID_PREFIX 'z' /* Auto anchors run z1, z2, ... 921122 */
extern HTStyleSheet * styleSheet; /* see StyleToy */
static int window_sequence = 0; /* For stacking windows neatly */
#define SLOTS 30 /* Number of position slots */
static HyperText * slot[SLOTS]; /* Ids of HT objects taking them */
#define NICE_HEIGHT 600.0 /* Allows a few windows to be stacked */
#define MAX_HEIGHT 720.0 /* Worth going bigger to get it all in */
#define MIN_HEIGHT 80.0 /* Mustn't lose the window! */
#define MIN_WIDTH 200.0
static HyperText * HT; /* Global pointer to self to allow C mixing */
+initialize
{
int i;
for (i=0; i<SLOTS; i++) slot[i] = 0;
return [super initialize];
}
// Get Application page layout's Page Width
// ----------------------------------------
//
// Returned in pixels
//
static float page_width()
{
PrintInfo * pi = [NXApp printInfo]; // Page layout details
NXCoord topMargin, bottomMargin, leftMargin, rightMargin;
const NXRect * paper = [pi paperRect]; // In points
[pi getMarginLeft:&leftMargin right:&rightMargin
top:&topMargin bottom:&bottomMargin]; /* In points */
return (paper->size.width - leftMargin - rightMargin);
}
/* Update the document and window according to the anchor info
*/
- syncWithAnchor
{
CONST char * title = HTAnchor_title(nodeAnchor);
if (title) [window setTitle:title];
else {
char * addr = HTAnchor_address((HTAnchor*)nodeAnchor);
[window setTitle:addr];
free(addr);
}
[self setEditable: HTList_indexOf(HTAnchor_methods(nodeAnchor),
HTAtom_for("PUT")) != (-1) ? YES : NO];
return self;
}
// Class methods
// -------------
//
// Build a HyperText GIVEN its nodeAnchor.
// --------------------------------------
//
//
+ newAnchor:(HTParentAnchor *)anAnchor
{
NXRect aFrame = {{0.0, 0.0}, {page_width(), NICE_HEIGHT}};
self = [super newFrame:&aFrame];
if (TRACE) printf("New node\n");
nextAnchorNumber = 0;
protection = 0; // Can do anything
format = WWW_HTML; // By default
[self setMonoFont: NO]; // By default
theRuns->chunk.growby = 16 * sizeof(theRuns->runs[0]); // efficiency
[self setDelegate: TheManager]; /* For changes */
nodeAnchor= anAnchor;
HTAnchor_setDocument(anAnchor, (void*) self);
[self syncWithAnchor];
return self;
}
// Instance Methods
// ----------------
// Free the hypertext.
- free
{
slot[slotNumber] = 0; // Allow slot to be reused
HTAnchor_setDocument(nodeAnchor, NULL);
return [super free];
}
// Read and set format
- (int) format {return format; }
- setFormat:(int)f {format = f; return self; }
// Useful diagnostic routine: Dump to standard output
// ---------------------------------------------------
//
// This first lists the runs up to and including the current run,
// then it lists the attributes of the current run.
//
- dump:sender
{
int pos; /* Start of run being scanned */
int sob=0; /* Start of text block being scanned */
NXRun * r = theRuns->runs;
NXTextBlock * block = firstTextBlock;
printf("Hypertext %p, selected(%i,%i)", self, sp0.cp, spN.cp);
if (delegate) printf(", has delegate");
printf(".\n");
printf(" Frame is at (%f, %f, size is (%f, %f)\n",
frame.origin.x, frame.origin.y,
frame.size.width, frame.size.height);
printf(" Text blocks and runs up to character %i:\n", sp0.cp);
for (pos = 0; pos<=sp0.cp; pos = pos+((r++)->chars)) {
while(sob <= pos) {
printf("%5i: Block of %i/%i characters at 0x%x starts `%10.10s'\n",
sob, block->chars, malloc_size(block->text), block->text,
block->text);
sob = sob + block->chars;
block = block->next;
}
printf("%5i: %3i of fnt=%i p=%i gy=%3.2f RGB=%i i=%i fl=%x\n",
pos, r->chars, (int)r->font, r->paraStyle,
r->textGray,r->textRGBColor, (int)(r->info),
*(int*)&(r->rFlags));
}
r--; /* Point to run for start of selection */
printf("\n Current run:\n\tFont name:\t%s\n", [r->font name]);
{
NXTextStyle *p = (NXTextStyle *)r->paraStyle;
if (!p) {
printf("\tNo paragraph style!\n");
} else {
int tab;
printf("\tParagraph style %i\n", p);
printf("\tIndents: first=%f, left=%f\n",
p->indent1st, p->indent2nd);
printf("\tAlignment type=%i, %i tabs:\n",
p->alignment, p->numTabs);
for (tab=0; tab<p->numTabs; tab++) {
printf("\t Tab kind=%i at %f\n",
p->tabs[tab].kind, p->tabs[tab].x);
}
}
}
printf("\n");
return self;
}
// Adjust Scrollers and Window size for current text size
// ------------------------------------------------------
//
//The scrollers are turned off if they possibly can be, to simplify the screen.
// If the text is editable, they have to be left on, although formatted text is
// allowed to wrap round, and so horizontal scroll bars are not necessary.
// The window size is adjusted as a function of the text size and scrollers.
//
// @@ Bug: The resize bar should be removed if there are no scrollers.
// This is difficult to do -- might have to make a new window.
//
- adjustWindow
{
#define MAX_WIDTH paperWidth
NXRect scroll_frame;
NXRect old_scroll_frame;
NXSize size;
BOOL scroll_X, scroll_Y; // Do we need scrollers?
ScrollView* scrollview = [window contentView];// Pick up id of ScrollView
float paperWidth = page_width(); // Get page layout width
[self syncWithAnchor];
[window disableFlushWindow]; // Prevent flashes
[self setVertResizable:YES]; // Can change size automatically
[self setHorizResizable:tFlags.monoFont];
[self calcLine]; // Wrap text to current text size
[self sizeToFit]; // Reduce size if possible.
if (maxY > MAX_HEIGHT) {
scroll_Y = YES;
size.height = NICE_HEIGHT;
} else {
scroll_Y = [self isEditable];
size.height = maxY < MIN_HEIGHT ? MIN_HEIGHT : maxY;
}
if (tFlags.monoFont) {
scroll_X = [self isEditable] || (maxX>MAX_WIDTH);
[self setNoWrap];
} else {
scroll_X = NO;
[self setCharWrap:NO]; // Word wrap please
}
if (maxX > MAX_WIDTH) {
size.width = MAX_WIDTH;
} else {
size.width = maxX < MIN_WIDTH ? MIN_WIDTH : maxX;
}
// maxX is the length of the longest line.
// It only represnts the width of the page
// needed if the line is quad left. If the longest line was
// centered or flush right, it may be truncated unless we resize
// it to fit.
if (!scroll_X) {
[self sizeTo:size.width:maxY];
[self calcLine];
[self sizeToFit]; // Algorithm found by trial and error.
}
// Set up the scroll view and window to match:
[ScrollView getFrameSize:&scroll_frame.size
forContentSize: &size
horizScroller: scroll_X
vertScroller: scroll_Y
borderType: NX_LINE];
[scrollview setVertScrollerRequired:scroll_Y];
[scrollview setHorizScrollerRequired:scroll_X];
// Has the frame size changed?
[scrollview getFrame:&old_scroll_frame];
if ( (old_scroll_frame.size.width != scroll_frame.size.width)
||(old_scroll_frame.size.height != scroll_frame.size.height)) {
// Now we want to leave the top left corner of the window unmoved:
#ifdef OLD_METHOD
NXRect oldframe;
[window getFrame:&oldframe];
[window sizeWindow:scroll_frame.size.width:scroll_frame.size.height];
[window moveTopLeftTo: oldframe.origin.x
: oldframe.origin.y + oldframe.size.height];
#else
NXRect newFrame;
scroll_frame.origin.x = 150 + (slotNumber % 10) * 30
+ ((slotNumber/10)%3)* 40;
scroll_frame.origin.y = 185 + NICE_HEIGHT - scroll_frame.size.height
- (slotNumber % 10) * 20
- ((slotNumber/10)%3)* 3;
[Window getFrameRect:&newFrame
forContentRect:&scroll_frame
style:NX_TITLEDSTYLE]; // Doesn't allow space for resize bar
newFrame.origin.y = newFrame.origin.y - 9.0;
newFrame.size.height = newFrame.size.height + 9.0; // For resize bar
[window placeWindow:&newFrame];
#endif
}
#ifdef VERSION_1_STRANGENESS
// In version 2, the format of the last run is overwritten with the format
// of the preceding run!
{
NXRect frm; /* Try this to get over "text strangeness" */
[self getFrame:&frm];
[self renewRuns:NULL text:NULL frame:&frm tag:0];
}
#endif
[window reenableFlushWindow];
[self calcLine]; /* Prevent messy screen */
[window display]; /* Ought to clean it up */
return self;
} /* adjustWindow */
// Set up a window in the current application for this hypertext
// -------------------------------------------------------------
- setupWindow
{
NXRect scroll_frame; // Calculated later
NXSize min_size = {300.0,200.0}; // Minimum size of text
NXSize max_size = {1.0e30,1.0e30}; // Maximum size of text
ScrollView * scrollview;
NXSize nice_size = { 0.0, NICE_HEIGHT }; // Guess height
nice_size.width = page_width();
[ScrollView getFrameSize:&scroll_frame.size
forContentSize: &nice_size
horizScroller:NO
vertScroller:YES
borderType: NX_LINE];
{
int i; /* Slot address */
for(i=0; (i<SLOTS) && slot[i]; i++) ; /* Find spare slot */
if (i=SLOTS) i = (window_sequence = (window_sequence+1) % SLOTS);
slot[i] = self;
slotNumber = i;
scroll_frame.origin.x = 150 + (slotNumber % 10) * 30
+ ((slotNumber/10)%3)* 40;
scroll_frame.origin.y = 185 - (slotNumber % 10) * 20
- ((slotNumber/10)%3)* 3;
}
// Build a window around the text in order to display it.
#define NX_ALLBUTTONS 7 // Fudge -- the followin methos is obsolete in 3.0:
window = [Window newContent: &scroll_frame
style: NX_TITLEDSTYLE
backing: NX_BUFFERED
buttonMask: NX_ALLBUTTONS
defer: NO]; // display now
[window setDelegate:self]; // Get closure warning
[window makeKeyAndOrderFront:self]; // Make it visible
[window setBackgroundGray: 1.0]; // White seems to be necessary.
scrollview = [ScrollView newFrame:&scroll_frame];
[scrollview setVertScrollerRequired:YES];
[scrollview setHorizScrollerRequired:NO]; // Guess.
[[window setContentView:scrollview] free]; // Free old view, size new one.
[scrollview setDocView:self];
[self setOpaque:YES]; // Suggested in the book
[self setVertResizable:YES]; // Changes size automatically
[self setHorizResizable:NO];
[self setMinSize:&min_size]; // Stop it shrinking to nought
[self setMaxSize:&max_size]; // Stop it being chopped when editing
[self notifyAncestorWhenFrameChanged: YES]; // Tell scrollview See QA 555
[window display]; // Maybe we will see it now
return self;
}
// Return Instance Variables
// -------------------------
- (HTParentAnchor*) nodeAnchor { return nodeAnchor; }
/* Return reference to a part of, or all of, this node
** ---------------------------------------------------
*/
// Generate an anchor for a given part of this node, giving it an
// arbitrary (numeric) name.
- (char *) new_unique_name
{
static char s[20];
sprintf(s,"%c%i",ANCHOR_ID_PREFIX, nextAnchorNumber++);
[delegate textDidChange:self];
return s;
}
// Check whether an anchor has been selected
// -----------------------------------------
- (HTChildAnchor *) anchorSelected
{
int sor;
NXRun *r, *s, *e; /* Scan, Start and end runs */
HTChildAnchor * a;
for (sor = 0, s=theRuns->runs;
sor+s->chars<=sp0.cp;
sor = sor+((s++)->chars)) ;
for (e=s; sor+e->chars<spN.cp; sor = sor+ (e++)->chars);
for(r=s; r<=e; r++) {
if (a= (HTChildAnchor *)r->info) return a;
}
if (TRACE) printf("HyperText: No anchor selected.\n");
return NULL;
}
// Public method: Generate an anchor for the selected text
// --------------------------------------------------------
//
// If the document is not editable, then nil is returned
// (unless the user asks for an existing anchor).
//
- (HTChildAnchor *) referenceSelected
{
HTChildAnchor * a;
HTStyle * style = HTStyleNew();
a = [self anchorSelected];
if (a) return a; /* User asked for existing one */
if ([self isEditable]) [window setDocEdited:YES];
else return NULL;
a = HTAnchor_findChildAndLink(nodeAnchor,
[self new_unique_name], NULL, NULL);
style->anchor = a;
[self applyStyle:style];
if (TRACE) printf("HyperText: New dest anchor %i from %i to %i.\n",
a, sp0.cp, spN.cp);
[delegate textDidChange:self];
return a;
}
// Generate a live anchor for the text, and link it to a given one
// ----------------------------------------------------------------
- (HTChildAnchor *) linkSelTo:(HTAnchor *)anAnchor
{
HTChildAnchor * a;
HTStyle * style = HTStyleNew();
if (!anAnchor) return NULL; /* Anchor must exist */
if ([self isEditable]) [window setDocEdited:YES];
else return NULL;
a = [self anchorSelected];
if (!a) {
a = HTAnchor_findChildAndLink(nodeAnchor, [self new_unique_name],
NULL, NULL);
if (TRACE) printf("HyperText: New source anchor %i from %i to %i.\n",
a, sp0.cp, spN.cp);
} else {
[self selectAnchor:a];
if (TRACE) printf("HyperText: Existing source anchor %i selected.\n",a);
}
style->anchor = a;
HTAnchor_link((HTAnchor*) a, anAnchor, 0); // Link it up
[self applyStyle:style]; // Will highlight it because linked
free(style);
return a;
}
// Purge anchor from selected text
// -------------------------------
//
// The anchor is left becuase in general we don't delete anchors.
// In any case, we would have to check whether all text referencing it
// was deleted.
//
- unlinkSelection
{
HTStyle * style = HTStyleNew();
if ([self isEditable]) [window setDocEdited:YES];
else return nil;
style->anchor = CLEAR_POINTER;
[self applyStyle:style];
free(style);
return self;
}
- (HTParentAnchor *) referenceAll
{
return nodeAnchor; // Just return the same one each time
}
// Select an anchor
// ----------------
//
// If there are any runs linked to this anchor, we select them. Otherwise,
// we just bring the window to the front.
- selectAnchor:(HTChildAnchor *)anchor
{
NXRun *s, *e; /* run for start, run for end */
int start, sor;
NXRun * limit = (NXRun *)((char *)theRuns->runs + theRuns->chunk.used);
for (sor=0, s=theRuns->runs; s<limit; sor = sor+(s++)->chars) {
if (s->info == (void *)anchor){
start = sor;
for (e=s; (e < limit)
&&((e+1)->info == (void*)anchor);
sor = sor+(e++)->chars);
[window makeKeyAndOrderFront: self];
[self setSel:start:sor+e->chars];
return [self scrollSelToVisible];
}
}
if (TRACE) printf("HT: Anchor has no explicitly related text.\n");
return [window makeKeyAndOrderFront: self];
}
//
// Return selected link (if any) selectedLink:
// -----------------------------
//
// This implementation scans down the list of anchors to find the first one
// on the list which is at least partially selected.
//
- (HTChildAnchor*) selectedLink
{
int sor; /* Start of run */
NXRun *r, *s, *e; /* Scan, Start and end runs */
HTChildAnchor * a;
int startPos, endPos;
for (sor = 0, s=theRuns->runs;
sor+s->chars<=sp0.cp;
sor = sor+((s++)->chars)) ;
startPos = sor; /* Start of s */
for (e=s; sor+e->chars<spN.cp; sor = sor+ (e++)->chars);
for(r=s, a=NULL; r<=e; startPos = startPos + (r++)->chars) {
a = (HTChildAnchor *)r->info;
if (a) break;
}
if (!a) {
if (TRACE) printf("HyperText: No anchor selected.\n");
return NULL;
}
// Extend/reduce selection to entire anchor
{
endPos = startPos + r->chars;
for (s=r; (HTChildAnchor *)((s-1)->info) == a; s--)
startPos = startPos-(s-1)->chars;
for (e=r; (HTChildAnchor *)((e+1)->info) == a; e++)
endPos = endPos+(e+1)->chars;
[self setSel:startPos:endPos];
}
return a;
}
/// Follow selected link (if any) followLink:
// -----------------------------
//
// Find selected link and follow it
- (HTChildAnchor*)followLink
{
HTChildAnchor * a = [self selectedLink];
if (!a) return a; // No link selected
if (HTLoadAnchor(HTAnchor_followMainLink((HTAnchor*)a)))
return a; // Try to follow link
if (TRACE) printf("HyperText: Can't follow anchor.\n");
return a; // ... but we did highlight it.
}
- setTitle:(const char *)aString
{
return [window setTitle:aString];
}
// STYLE METHODS
// =============
//
// Find Unstyled Text
// ------------------
//
// We have to check whether the paragraph style for each run is one
// on the style sheet.
//
- selectUnstyled: (HTStyleSheet *)sheet
{
NXRun * r = theRuns->runs;
int sor;
for (sor=0; sor<textLength; r++) {
if (!HTStyleForParagraph(sheet, r->paraStyle)) {
[self setSel:sor:sor+r->chars]; /* Select unstyled run */
return self;
}
sor = sor+r->chars;
}
return nil;
}
// Copy a style into a run
// -----------------------
static void apply(HTStyle * style, NXRun * r)
{
if (style->font) {
r->font = style->font;
}
if (style->paragraph) {
r->paraStyle = style->paragraph;
}
if (style->anchor) {
r->info = (style->anchor == CLEAR_POINTER) ? 0 : (style->anchor);
}
if (style->textGray>=0)
r->textGray = style->textGray;
r->rFlags.underline = NO;
if (r->info) {
// r->textGray = 0.166666666; /* Slightly grey - horrid */
if (HTAnchor_followMainLink((HTAnchor *)(r->info))) {
// r->textGray = NX_DKGRAY; /* Anchor highlighting */
r->rFlags.underline = YES;
}
}
r->rFlags.dummy = (r->info != 0); /* Keep track for typingRun */
if (style->textRGBColor>=0)
r->textRGBColor = style->textRGBColor;
}
// Check whether copying a style into a run will change it
// -------------------------------------------------------
static BOOL willChange(HTStyle * style, NXRun *r)
{
if (r->font != style->font) return YES;
if (style->textRGBColor>=0)
if (r->textRGBColor != style->textRGBColor) return YES;
if (style->textGray>=0)
if (r->textGray != style->textGray) return YES;
if (style->paragraph) {
if (r->paraStyle != style->paragraph) return YES;
}
if (style->anchor) {
if (r->info != style->anchor) return YES;
}
return NO;
}
// Update a style
// --------------
//
//
- updateStyle:(HTStyle *)style
{
NXRun * r = theRuns->runs;
int sor;
for (sor=0; sor<textLength; r++) {
if (r->paraStyle == style->paragraph)
apply(style, r);
sor = sor+r->chars;
}
[self calcLine];
[window display];
return nil;
}
// Delete an anchor from this node, without freeing it.
// ----------------
//
//
- disconnectAnchor:(HTChildAnchor *)anchor
{
NXRun * r = theRuns->runs;
int sor;
for (sor=0; sor<textLength; r++) {
if (r->info == (void *)anchor)
r->info = 0;
r->textGray = NX_BLACK;
sor = sor+r->chars;
}
[window display];
return nil;
}
// Find start of paragraph
// -----------------------
//
// Returns the position of the character after the newline, or 0.
//
- (int) startOfParagraph: (int) pos
{
NXTextBlock * block;
int sob;
unsigned char * p;
for(block=firstTextBlock, sob=0; sob+block->chars <=pos; block=block->next)
sob = sob + block->chars;
for(p=block->text+(pos-sob)-1; p >= block->text; p--)
if (*p == '\n') return sob + (p - block->text) +1 ;/* Position of newline */
while (block->prior) {
block = block->prior;
sob = sob - block->chars;
for(p=block->text+(block->chars-1); p>=block->text; p--)
if (*p == '\n') return sob + (p-block->text)+1;/* Position of newline */
}
return 0;
}
// Find end of paragraph
// -----------------------
//
// Returns the position after the newline, or the length of the text.
// Note that any number of trailing newline characters are included in
// this paragraph .. basically because the text object does not support
// the concept of space after or before paragraphs, so extra paragrpah
// marks must be used.
//
- (int) endOfParagraph: (int) pos
{
NXTextBlock * block;
int sob;
unsigned char * p;
BOOL found_newline = NO;
if (pos>=textLength) return textLength;
for(block=firstTextBlock, sob=0; sob+block->chars <=pos; block=block->next)
sob = sob + block->chars; // Find text block for pos
p = block->text+(pos-sob); // Start part way through this one
while (block) {
for(; p < block->text+block->chars; p++) {
if (found_newline) {
if (*p != '\n')
return sob + (p-block->text); /* Position after newline */
} else {
if (*p == '\n') {
found_newline = YES;
}
}
}
sob = sob + block->chars; /* Move to next block */
block = block->next;
if (block) p = block->text;
}
return textLength;
}
// Do two runs imply the same format?
// ----------------------------------
BOOL run_match(NXRun* r1, NXRun *r2)
{
return (r1->font == r2->font)
&& (r1->paraStyle == r2->paraStyle)
&& (r1->textGray == r2->textGray)
&& (r1->textRGBColor == r2->textRGBColor)
&& (r1->superscript == r2->superscript)
&& (r1->subscript == r2->subscript)
&& (r1->info == r2->info)
&& (*(int *)&r1->rFlags == *(int*)&r2->rFlags);
}
// Check Consecutive runs and merge if necessary
// ---------------------------------------------
//
// If the runs match in EVERY way, they are combined into one, and
// all the other runs are shuffled down.
//
- (BOOL) mergeRun: (NXRun *) run
{
NXRun * r, *last;
if (run_match(run, run+1) ) {
if (TRACE) printf("HT: Merging run %p\n", run);
run->chars = run->chars +(run+1)->chars;
last = ((NXRun *)((char*)theRuns->runs + theRuns->chunk.used))-1;
for(r=run+1; r<last; r++) r[0] = r[1];
theRuns->chunk.used = theRuns->chunk.used - sizeof(*r);
return YES;
}
return NO;
}
// Apply style to a given region
// -----------------------------
//
// Note that one should not have two consecutive runs of the same style,
// nor any zero length runs. We have a little calculation, therefore,
// in order to work out how many runs will eventually be needed:
// this may be more or less than we started with.
// Remember that appling a style to a run may or may not change it.
//
// PS: Actually, we notice that text insertion does leave two consecutive
// runs the same in the Text object, but deletion cleans up.
- applyStyle:(HTStyle *)style from:(int)start to:(int)end
{
int pos; /* Character position within text */
int increase; /* Number of runs to be split */
int new_used; /* New number of bytes in runs */
BOOL need_run_before, need_run_after;/* Sometimes we don't need them */
int run_before_start, run_after_end;/* Start of run_before etc */
NXRun * s, *e; /* Start and end run */
NXRun *p; /* Pointer to run being read */
NXRun *w; /* Pointer to run being written */
NXRun *r; /* Pointer to end of runs */
if (start == end) {
apply(style, &typingRun); /* Will this work? */
if (TRACE) printf("Style applied to typing run\n");
return nil; /* Can't operate on nothing */
}
// First we determine in which runs the first and last characters to
// be changed lie.
for (pos=0, s=theRuns->runs; pos+s->chars<=start;
pos = pos+((s++)->chars)) /*loop*/;
/* s points to run containing char after selection start */
run_before_start = pos;
for (e=s; pos+e->chars < end; pos = pos+((e++)->chars)) ; /* Find end run */
/* e points to run containing character before selection end */
run_after_end = pos+e->chars;
r = (NXRun *) (((char *)(theRuns->runs)) +theRuns->chunk.used); /* The end*/
if (TRACE) {
printf("Runs: used=%i, elt. size=%i, %i elts, total=%i\n",
theRuns->chunk.used, sizeof(*r), r - theRuns->runs,
(r-theRuns->runs)*sizeof(*r) );
printf(" runs at %i, r=%i. textLength:%i, r ends at:%i\n",
theRuns->runs, r, textLength, pos);
}
// Move up runs as necessary in order to make room for the splitting
// of the start and end runs into two. We only do this if necessary.
if (!willChange(style, s))
start = run_before_start; /* No run before is needed now */
need_run_before = (start>run_before_start);
if (!willChange(style, e))
end = run_after_end; /* No run after is needed now */
need_run_after = (end < run_after_end);
if (TRACE) printf(
"Run s=%i, starts at %i; changing (%i,%i); Run e=%i ends at %i\n",
s-theRuns->runs, run_before_start, start, end, e-theRuns->runs, run_after_end);
increase = need_run_after + need_run_before;
if (increase) {
new_used = theRuns->chunk.used + increase*sizeof(*r);
if (new_used> theRuns->chunk.allocated) {
NXRun* old = theRuns->runs;
theRuns = (NXRunArray*)NXChunkGrow(&theRuns->chunk, new_used);
if (theRuns->runs !=old) { /* Move pointers */
if (TRACE) printf("HT:Apply style: moving runs!\n");
e = theRuns->runs + (e-old);
r = theRuns->runs + (r-old);
s = theRuns->runs + (s-old);
}
}
for (p=r-1; p>=e; p--) p[increase] = p[0]; /* Move up the runs after */
r = r+increase; /* Point to after them 910212*/
/* p = e-1 */
if (need_run_after) {
e = e + increase-1; /* Point last to be changed */
e[0] = e[1]; /* Copy the last run */
e[1].chars = run_after_end - end;
e[0].chars = e[0].chars - e[1].chars; /* Split the run into two */
}
if (need_run_before) {
for(;p>=s; p--) p[1] = p[0]; /* Move runs up, copying 1st*/
s[0].chars = start - run_before_start; /* Split the run into two */
if (need_run_after && (s+1==e)) { /* If only one middle run */
s[1].chars = end-start; /* The run we need */
} else {
s[1].chars = s[1].chars - s[0].chars; /* The remainder */
}
s++; /* Move on to point to first run to be changed */
if (!need_run_after) e++; /* First to be changed */
}
theRuns->chunk.used = new_used;
} /* end if increase */
// We consider the bit of text which is to be styled, s thru e.
// We scan through, first, applying the style, until we find two runs which
// need to be merged.
p=s;
if (p==theRuns->runs) {
apply(style, p++); /* Don't merge with run -1! */
}
for(; p<=e; p++) {
apply(style, p);
if (run_match(p,p-1)) {
break;
}
}
// Once we have merged two runs, we have to copy the rest of them across,
// merging others as necessary.
w = p-1; /* w now points to last written run */
for(;p<=e; p++) {
apply(style, p);
if (run_match(p,w)) {
w->chars = w->chars+p->chars; /* Combine with w */
} else {
w++; /* or skip */
*w = *p; /* and keep a copy */
}
}
// Now, is any runs were merged, we have to copy the rest of the runs down
// and decrease the size of the chunk.
w++; /* Point to next to be written */
if (w<p) { /* If any were moved, */
for(;p<r;) *w++ = *p++; /* Move the following runs down */
theRuns->chunk.used = (char*)w - (char*)theRuns->runs;
}
[self calcLine]; /* Update line breaks */
return [window display]; /* Update window */
}
// Apply a style to the selection
// ------------------------------
//
// If the style is a paragraph style, the
// selection is extended to encompass a number of paragraphs.
//
- applyStyle:(HTStyle *)style
{
int start, end;
if (TRACE) printf("Applying style %i to (%i,%i)\n",
style, sp0.cp, spN.cp);
if (sp0.cp<0) { /* No selection */
return [self applyStyle:style from:0 to:0]; /* Apply to typing run */
}
if (!style) return nil;
if ([self isEditable]) [window setDocEdited:YES];
else return nil;
start = sp0.cp;
end = spN.cp;
if (style->paragraph) { /* Extend to an integral number of paras. */
start = [self startOfParagraph:start];
end = [self endOfParagraph:end];
}
return [self applyStyle:style from:start to:end];
}
// Apply style to all similar text
// -------------------------------
//
//
- applyToSimilar:(HTStyle*)style
{
NXRun * r = theRuns->runs;
int sor;
NXRun old_run;
for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */
old_run = *(r-1); /* Point to run for start of selection */
if (TRACE) printf(
"Applying style %i to unstyled text similar to (%i,%i)\n",
style, sp0.cp, spN.cp);
for(r=theRuns->runs;
(char*)r-(char*)theRuns->runs < theRuns->chunk.used; r++) {
if (r->paraStyle == old_run.paraStyle) {
if(TRACE) printf(" Applying to run %i\n", r);
apply(style,r);
if (r!=theRuns->runs){
if ([self mergeRun:r-1]) r--; /* Do again if shuffled down */
}
}
}
[self calcLine];
[window display];
return self;
}
// Pick up the style of the selection
// ----------------------------------
- (HTStyle *) selectionStyle:(HTStyleSheet *) sheet
{
NXRun * r = theRuns->runs;
int sor;
for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */
r--; /* Run for start of selection */
return HTStyleForRun(sheet, r); /* for start of selection */
}
// Another replaceSel method, this time using styles:
// -------------------------------------------------
//
// The style is as given, or where that is not defined, as the
// current style of the selection.
- replaceSel: (const char *)aString style:(HTStyle*)aStyle
{
NXRun * r = theRuns->runs;
int sor;
NXRunArray newRuns;
for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */
r--; /* Run for start of selection */
newRuns.runs[0] = *r; /* Copy it */
newRuns.chunk.used = sizeof(*r); /* 1 run used */
apply(aStyle, newRuns.runs); /* change it */
newRuns.runs->chars = strlen(aString); /* Match the size to the string */
return [self replaceSel:aString length:newRuns.runs->chars runs:&newRuns];
}
// Read in as Plain Text readText:
// ---------------------
//
// This method overrides the method of Text, so as to force a plain text
// hypertext to be monofont and fixed width. Also, the window is updated.
//
- readText: (NXStream *)stream
{
// [self setMonoFont:YES]; Seems to leave it in a strange state
[self setHorizResizable:YES];
[self setNoWrap];
[self setFont:[Font newFont:"Ohlfs" size:10.0]]; // @@ Should be XMP
[super readText:stream];
format = WWW_PLAINTEXT; // Remember
#ifdef NOPE
{
NXRect frm; /* Try this to get over "text strangeness" */
[self getFrame:&frm]; /* on plain text only Aug 91 */
[self renewRuns:NULL text:NULL frame:&frm tag:0];
}
#endif
[self adjustWindow];
return self;
}
// Read in as Rich Text readRichText:
// ---------------------
//
// This method overrides the method of Text, so as to force a plain text
// hypertext to be monofont and fixed width. Also, the window is updated.
//
- readRichText: (NXStream *)stream
{
id status = [super readRichText:stream];
[self adjustWindow];
format = WWW_RICHTEXT; // Remember
return status;
}
// Window Delegate Methods
// =======================
// Prevent closure of edited window without save
//
- windowWillClose:sender
{
int choice;
if (![window isDocEdited]) return self;
choice = NXRunAlertPanel("Close", "Save changes to `%s'?",
"Yes", "No", "Don't close", [window title]);
if (choice == NX_ALERTALTERNATE) return self;
if (choice == NX_ALERTOTHER) return nil;
return nil; //[server saveNode:self]; @@@@@@@@ server!!!
}
// Change configuration as window becomes key window
//
- windowDidBecomeMain:sender
{
return [delegate hyperTextDidBecomeMain:self];
}
/* FORMAT CONVERSION FROM SGML
** ===========================
**
** As much as possible, this is written in C for portability. It is in a separate
** include file which could be used elsewhere.
*/
/* Input procedure for printing a trace as we go
*/
/*#define NEXT_CHAR NXGetc(sgmlStream)
#define BACK_UP NXUngetc(sgmlStream)
*/
/* Globals for using many subroutines within a method
*/
static HyperText * HT; /* Pointer to self for C */
// Inputting from the text object:
// ------------------------------
static unsigned char * read_pointer; /* next character to be read */
static unsigned char * read_limit;
static NXTextBlock * read_block;
void start_input()
{
read_block = HT->firstTextBlock;
read_pointer = read_block->text; /* next character to be read */
read_limit = read_pointer+read_block->chars;
}
unsigned char next_input_block()
{
char c = *read_pointer;
read_block = read_block->next;
if (!read_block) read_block = HT->firstTextBlock; /* @@@ FUDGE */
read_pointer = read_block->text;
read_limit = read_pointer + read_block->chars;
return c;
}
#define START_INPUT start_input()
#define NEXT_TEXT_CHAR (read_pointer+1==read_limit? next_input_block():*read_pointer++)
// Outputting to the text object
// =============================
//
// These macros are used by the parse routines
//
#define BLOCK_SIZE NX_TEXTPER /* Match what Text seems to use */
static NXTextBlock *write_block; /* Pointer to block being filled */
static unsigned char *write_pointer; /* Pointer to next characetr to be written */
static unsigned char *write_limit; /* Pointer to the end of the allocated area*/
static NXRun * lastRun; /* Pointer to the run being appended to */
static int original_length; /* of text */
static HTStyle * appending_style = 0;
void end_output(); /* Forward */
#define OUTPUT(c) { *write_pointer++ = (c); \
if (write_pointer==write_limit) {end_output(); append_start_block(); }}
#define OUTPUTS(string) {const char * p; for(p=(string);*p;p++) OUTPUT(*p);}
#define START_OUTPUT append_begin()
#define FINISH_OUTPUT finish_output()
#define LOADPLAINTEXT loadPlainText()
#define SET_STYLE(s) set_style(s)
// Allocate a text block to accumulate text
//
// Bugs:
// It might seem logical to set the "malloced" bit to 1, because the text block
// has been allocted with malloc(). However, this crashes the program as at the
// next edit of the text, the text object frees the block while still using it.
// Chaos results, sometimes corrupting the stack and/or looping for ages. @@
// We therefore set it to zero! (This might have been something else -TBL)
//
void append_start_block()
{
NXTextBlock *previous_block=write_block; /* to previous write block */
if (TRACE)printf(" Starting to append new block.\n");
lastRun = ((NXRun*) ((char*)HT->theRuns->runs +
HT->theRuns->chunk.used))-1;
write_block = (NXTextBlock*)malloc(sizeof(*write_block));
write_block->tbFlags.malloced=0; /* See comment above */
write_block->text = (unsigned char *)malloc(BLOCK_SIZE);
write_block->chars = 0; // For completeness: not used.
write_pointer = write_block->text;
write_limit = write_pointer + BLOCK_SIZE;
// Add the block into the linked list after previous block:
write_block->prior = previous_block;
write_block->next = previous_block->next;
if (write_block->next) write_block->next->prior = write_block;
else HT->lastTextBlock = write_block;
previous_block->next = write_block;
}
// Start the output process altogether
//
void append_begin()
{
if (TRACE)printf("Begin append to text.\n");
appending_style = NULL; /* Bug @@@@ */
[HT setText:""]; // Delete everything there
original_length = HT->textLength;
if (TRACE) printf("Text now contains %i characters\n", original_length);
lastRun = ((NXRun*) ((char*)HT->theRuns->runs +
HT->theRuns->chunk.used))-1;
// Use the last existing text block:
write_block = HT->lastTextBlock;
// It seems that the Text object doesn't like to be empty: it always wants to
// have a newline in at least. However, we need it seriously empty and so we
// forcible empty it. CalcLine will crash if called with it in this state.
if (original_length==1) {
if (TRACE) printf("HT: Clearing out single character from Text.\n");
lastRun->chars = 0; /* Empty the run */
write_block->chars = 0; /* Empty the text block */
HT->textLength = 0; /* Empty the whole Text object */
original_length = 0; /* Note we have cleared it */
}
write_pointer = write_block->text+write_block->chars;
write_limit = write_pointer + BLOCK_SIZE;
}
// Insert vertical white space
//
void paragraph_gap(HTStyle * next)
{
if ((next->paragraph)
&& appending_style
&& (appending_style->paragraph != next->paragraph)
&& next) {
int i;
float space = appending_style->spaceAfter > next->spaceBefore ?
appending_style->spaceAfter : next->spaceBefore; /* max */
int newlines = (space/appending_style->paragraph->lineHt) + 1;
for(i=0; i<newlines; i++) OUTPUT('\n'); /* Rather approximate! */
}
}
// Set a style for new text
//
void set_style(HTStyle *style)
{
if (!style) {
if (TRACE) printf("set_style: style is null!\n");
return;
}
if (TRACE) printf(" Changing to style `%s' -- %s change.\n",
style->name, willChange(style, lastRun) ? "will" : "won't");
if (willChange(style, lastRun)) {
int size;
paragraph_gap(style);
size = (write_pointer - write_block->text);
lastRun->chars = lastRun->chars + size - write_block->chars;
write_block->chars = size;
if (lastRun->chars) {
int new_used = (((char *)(lastRun+2)) - (char*)HT->theRuns->runs);
if (new_used > HT->theRuns->chunk.allocated) {
if (TRACE) printf(" HT: Extending runs.\n");
HT->theRuns = (NXRunArray*)NXChunkGrow(
&HT->theRuns->chunk, new_used);
lastRun = ((NXRun*) ((char*)HT->theRuns->runs +
HT->theRuns->chunk.used))-1;
}
lastRun[1]=lastRun[0];
lastRun++;
HT->theRuns->chunk.used = new_used;
}
apply(style, lastRun);
lastRun->chars = 0; /* For now */
}
if (style->paragraph) appending_style = style;
}
// Transfer text to date to the Text object
// ----------------------------------------
void end_output()
{
int size = (write_pointer - write_block->text);
if (TRACE)printf(
" HT: Adding block of %i characters, starts: `%.20s...'\n",
size, write_block->text);
lastRun->chars = lastRun->chars + size - write_block->chars;
write_block->chars = size;
HT->textLength = HT->textLength + size;
}
// Finish altogether
// -----------------
void finish_output()
{
int size;
if (write_pointer == write_block->text && !write_block->prior) {
OUTPUT('\n'); /* Avoid empty object */
}
size = write_pointer - write_block->text;
if (size==0) {
if (write_block->prior) { /* If there are other blocks */
HT->lastTextBlock = write_block->prior;/* Remove empty text block */
write_block->prior->next = 0;
free(write_block->text);
free(write_block);
}
} else {
end_output();
}
// get rid of zero length run if any
if (lastRun->chars==0) { /* Chop off last run */
HT->theRuns->chunk.used= (char*)lastRun - (char*)HT->theRuns;
}
// calcLine requires that the last character be a newline!
{
unsigned char * p = HT->lastTextBlock->text +
HT->lastTextBlock->chars - 1;
if (*p != '\n') {
if (TRACE)
printf(
"HT: Warning: Last character was %i not newline: overwriting!\n",
*p);
*p = '\n';
}
}
[HT adjustWindow]; /* Adjustscrollers and window size */
}
// Methods enabling an external parser to add styled data
// ------------------------------------------------------
//
// These use the macros above in the same way as a built-in parser would.
//
- appendBegin
{
HT = self;
START_OUTPUT;
return self;
}
- appendStyle:(HTStyle *) style
{
SET_STYLE(style);
return self;
}
- appendText: (const char *)text
{
OUTPUTS(text);
return self;
}
/* Append character takes care of white space for paragraph marks.
*/
- appendCharacter: (char)ch
{
if ((ch=='\n') && appending_style && appending_style->paragraph) {
int newlines = (
((appending_style->spaceBefore > appending_style->spaceAfter) ?
appending_style->spaceBefore : appending_style->spaceAfter) /
appending_style->paragraph->lineHt) + 1;
int i;
for(i=0; i<newlines; i++) OUTPUT('\n'); /* approximate! @@ */
/* OUTPUTS(appending_style->paragraph_text); */
} else OUTPUT(ch);
return self;
}
- appendEnd
{
FINISH_OUTPUT;
return self;
}
// Begin an anchor
- (HTChildAnchor *)appendBeginAnchor: (HTChildAnchor *) a;
{
HTStyle * style = HTStyleNew();
style->anchor = a;
HTAnchor_makeLastChild((HTChildAnchor *)style->anchor); /* Put in correct order */
SET_STYLE(style); /* Start anchor here */
free(style);
return a;
}
- appendEndAnchor // End it
{
HTStyle * style = HTStyleNew();
style->anchor = CLEAR_POINTER;
SET_STYLE(style); /* End anchor here */
free(style);
return self;
}
// Reading from a NeXT stream
// --------------------------
#ifdef OLD_CODE
#define END_OF_FILE NXAtEOS(sgmlStream)
#define NEXT_CHAR NXGetc(sgmlStream)
#define BACK_UP NXUngetc(sgmlStream)
#endif
#include "ParseHTML.h"
// Methods overriding Text methods
// ===============================
//
// Respond to mouse events
// -----------------------
//
// The first click will have set the selection point. On the second click,
// we follow a link if possible, otherwise we allow Text to select a word as usual.
//
- mouseDown:(NXEvent*)theEvent
{
if (theEvent->data.mouse.click != 2) return [super mouseDown:theEvent];
if (![self followLink]) return [super mouseDown:theEvent];
return self;
}
// The following are necessary to undo damage done by the Text object
// in version 2.0 of NeXTStep. For some reason, iff the "info" is
// nonzero, text typed in is given
// a different copy of the typingRun parastyle, and zero anchor info.
// Otherwise, the current run is broken in two at the insertion point,
// but no changes made to the run contents.
// The problem with simply repairing is that many runs will be made inside
// an anchor.
// We have to use a "dummy" flag to mean "This has an anchor: be careful!"
// This is horrible.
- keyDown:(NXEvent*)theEvent
#ifdef TRY1
{
id result;
NXTextStyle *typingPara = typingRun.paraStyle;
int originalLength = textLength;
int originalStart = sp0.cp;
int originalEnd = spN.cp;
result = [super keyDown:theEvent];
{
int inserted = originalEnd-originalStart +
textLength-originalLength;
if (TRACE) printf(
"KeyDown, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n",
originalLength, originalStart, originalEnd,
textLength, sp0.cp, spN.cp);
if (inserted>0) {
NXRun * s;
int pos;
int start = sp0.cp-inserted;
for (pos=0, s=theRuns->runs; pos+s->chars<=start;
pos = pos+((s++)->chars)) /*loop*/;
// s points to run containing first char of insertion
if (pos!=start)
printf(
"HT: Strange: inserted %i at %i, start of run=%i !!\n",
inserted, start, pos);
if (s > theRuns->runs) { /* ie s-1 is valid */
s->paraStyle = typingPara; /* Repair damage to runs */
/* What about freeing the old paragraph style? @@ */
s->info = (s-1)->info;
s->rFlags.dummy = 1; /* Pass on flag */
}
}
}
return result;
}
#else
// The typingRun field does not seem to reliably reflect the
// format which would be appropriate if typing were to occur.
// We have to use our own.
{
NXRun run;
{
NXRun * s; /* To point to run BEFORE selection */
int pos;
/* If there is a nonzero selection, take the run containing the
** first character. If the selection is empty, take the run containing the
** character before the selection.
*/
if (sp0.cp == spN.cp) {
for (pos=0, s=theRuns->runs; pos+s->chars<sp0.cp; /* Before */
pos = pos+((s++)->chars)) /*loop*/;
} else {
for (pos=0, s=theRuns->runs; pos+s->chars<=sp0.cp; /* First ch */
pos = pos+((s++)->chars)) /*loop*/;
}
/* Check our understanding */
if (typingRun.paraStyle != 0) {
if (typingRun.paraStyle != s->paraStyle)
printf("WWW: Strange: Typing run has bad style.\n");
if ((s->info != 0)
&& (typingRun.info != s->info))
printf(
"WWW: Strange: Typing run has bad anchor info.\n");
}
typingRun = *s; /* Copy run to be used for insertion */
run = *s; /* save a copy */
}
if (!run.rFlags.dummy) return [super keyDown:theEvent]; // OK!
{
id result;
int originalLength = textLength;
int originalStart = sp0.cp;
int originalEnd = spN.cp;
result = [super keyDown:theEvent];
/* Does it really change? YES!
*/
if (TRACE) {
if (typingRun.info != run.info) printf(
"Typing run info was %p, now %p !!\n",
run.info, typingRun.info);
if (typingRun.paraStyle != run.paraStyle) printf(
"Typing run paraStyle was %p, now %p !!\n",
run.paraStyle, typingRun.paraStyle);
}
/* Patch the new run if necessary:
*/
{
int inserted = originalEnd-originalStart +
textLength-originalLength;
if (TRACE) printf(
"KeyDown, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n",
originalLength, originalStart, originalEnd,
textLength, sp0.cp, spN.cp);
if (inserted>0) {
NXRun * s;
int pos;
int start = sp0.cp-inserted;
for (pos=0, s=theRuns->runs; pos+s->chars<=start;
pos = pos+((s++)->chars)) /*loop*/;
// s points to run containing first char of insertion
if (pos!=start) { /* insert in middle of run */
if (TRACE) printf(
"HT: Inserted %i at %i, in run starting at=%i\n",
inserted, start, pos);
} else { /* inserted stuff starts run */
if (TRACE) printf ("Patching info from %d to %d\n",
s->info, run.info);
s->info = run.info;
s->paraStyle = run.paraStyle; /* free old one? */
s->rFlags.dummy = 1;
}
} /* if inserted>0 */
} /* block */
return result;
}
}
#endif
// After paste, determine paragraph styles for pasted material:
// ------------------------------------------------------------
- paste:sender;
{
id result;
int originalLength = textLength;
int originalStart = sp0.cp;
int originalEnd = spN.cp;
HTChildAnchor * typingInfo;
result = [super paste:sender]; // Do the paste
{
int inserted = originalEnd-originalStart +
textLength-originalLength;
if (TRACE) printf(
"Paste, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n",
originalLength, originalStart, originalEnd,
textLength, sp0.cp, spN.cp);
if (inserted>0) {
NXRun *s, *r;
int pos;
int start = sp0.cp-inserted;
for (pos=0, s=theRuns->runs; pos+s->chars<=start;
pos = pos+((s++)->chars)) /*loop*/;
// s points to run containing first char of insertion
if (pos!=sp0.cp-inserted)
printf("HT paste: Strange: insert@%i != run@%i !!\n",
start, pos);
if (s > theRuns->runs) typingInfo = (s-1)->info;
else typingInfo = 0;
for (r=s; pos+r->chars<sp0.cp; pos=pos+(r++)->chars) {
r->paraStyle = HTStyleForRun(styleSheet, r)->paragraph;
r->info = typingInfo;
}
}
}
return result;
}
@end